package f.h.dragon.datastructures.bplustree;

import java.io.RandomAccessFile;
import java.util.*;

/**
 * BplusTreeBytes: Summary description for BplusTreeBytes.
 *
 * @author Wenlong Meng(wenlong.meng@gmail.com)
 * @version 1.0 at 2012/04/27
 * @since 1.0
 */
public class BplusTreeBytes implements IByteTree {
	BplusTreeLong tree;
	LinkedFile archive;
	Hashtable<Long, Long> freeChunksOnCommit = new Hashtable<Long, Long>();
	Hashtable<Long, Long> freeChunksOnAbort = new Hashtable<Long, Long>();
	static int DEFAULTBLOCKSIZE = 1024;
	static int DEFAULTNODESIZE = 32;

	public BplusTreeBytes(BplusTreeLong tree, LinkedFile archive) {
		this.tree = tree;
		this.archive = archive;
	}

	public static RandomAccessFile makeFile(String path)
			throws Exception {
		java.io.File f = new java.io.File(path);
		if (f.exists()) {
			// System.out.println("<br>				DELETING FILE "+path);
			f.delete();
		}
		return new RandomAccessFile(path, "rw");
	}

	public static BplusTreeBytes initialize(String treefileName,
			String blockfileName, int KeyLength, int CultureId, int nodesize,
			int buffersize) throws Exception {
		RandomAccessFile treefile = makeFile(treefileName);
		RandomAccessFile blockfile = makeFile(blockfileName);
		return initialize(treefile, blockfile, KeyLength, CultureId, nodesize,
				buffersize);
	}

	public static BplusTreeBytes initialize(String treefileName,
			String blockfileName, int KeyLength, int CultureId)
			throws Exception {
		RandomAccessFile treefile = makeFile(treefileName);
		RandomAccessFile blockfile = makeFile(blockfileName);
		return initialize(treefile, blockfile, KeyLength, CultureId);
	}

	public static BplusTreeBytes initialize(String treefileName,
			String blockfileName, int KeyLength) throws Exception {
		RandomAccessFile treefile = makeFile(treefileName);
		RandomAccessFile blockfile = makeFile(blockfileName);
		return initialize(treefile, blockfile, KeyLength);
	}

	public static BplusTreeBytes initialize(RandomAccessFile treefile,
			RandomAccessFile blockfile, int KeyLength, int CultureId,
			int nodesize, int buffersize) throws Exception {
		BplusTreeLong tree = BplusTreeLong.initializeInStream(treefile,
				KeyLength, nodesize, CultureId);
		LinkedFile archive = LinkedFile.initializeLinkedFileInStream(blockfile,
				buffersize);
		return new BplusTreeBytes(tree, archive);
	}

	public static BplusTreeBytes initialize(RandomAccessFile treefile,
			RandomAccessFile blockfile, int KeyLength, int CultureId)
			throws Exception {
		return initialize(treefile, blockfile, KeyLength, CultureId,
				DEFAULTNODESIZE, DEFAULTBLOCKSIZE);
	}

	public static BplusTreeBytes initialize(RandomAccessFile treefile,
			RandomAccessFile blockfile, int KeyLength) throws Exception {
		int CultureId = BplusTreeLong.INVARIANTCULTUREID;
		return initialize(treefile, blockfile, KeyLength, CultureId,
				DEFAULTNODESIZE, DEFAULTBLOCKSIZE);
	}

	public static BplusTreeBytes reOpen(RandomAccessFile treefile,
			RandomAccessFile blockfile) throws Exception {
		BplusTreeLong tree = BplusTreeLong.setupFromExistingStream(treefile);
		LinkedFile archive = LinkedFile.setupFromExistingStream(blockfile);
		return new BplusTreeBytes(tree, archive);
	}

	public static BplusTreeBytes reOpen(String treefileName,
			String blockfileName, String access) throws Exception {
		RandomAccessFile treefile = new RandomAccessFile(
				treefileName, access);
		RandomAccessFile blockfile = new RandomAccessFile(
				blockfileName, access);
		return reOpen(treefile, blockfile);
	}

	public static BplusTreeBytes reOpen(String treefileName,
			String blockfileName) throws Exception {
		return reOpen(treefileName, blockfileName, "rw");
	}

	public static BplusTreeBytes readOnly(String treefileName,
			String blockfileName) throws Exception {
		return reOpen(treefileName, blockfileName, "r");
	}

	// / <summary>
	// / Use non-culture sensitive total order on binary Strings.
	// / </summary>
	public void noCulture() {
		// not relevant to java implementation currently.
		// this.tree.DontUseCulture = true;
		// this.tree.cultureContext = null;
	}

	public int maxKeyLength() throws Exception {
		return this.tree.maxKeyLength();
	}

	// #region ITreeIndex Members

	public int compare(String left, String right) throws Exception {
		return this.tree.compare(left, right);
	}

	public void shutdown() throws Exception {
		this.tree.shutdown();
		this.archive.Shutdown();
	}

	public void recover(boolean CorrectErrors) throws Exception {
		this.tree.recover(CorrectErrors);
		Hashtable<Long, String> chunksInUse = new Hashtable<Long, String>();
		String key = this.tree.firstKey();
		while (key != null) {
			Long buffernumber = new Long(this.tree.get(key));
			if (chunksInUse.containsKey(buffernumber)) {
				throw new BplusTreeException("buffer number " + buffernumber
						+ " associated with more than one key '" + key
						+ "' and '" + chunksInUse.get(buffernumber) + "'");
			}
			chunksInUse.put(buffernumber, key);
			key = this.tree.nextKey(key);
		}
		// also consider the un-deallocated chunks to be in use
		// foreach (Object thing in this.FreeChunksOnCommit)
		// for (int i=0; i<this.FreeChunksOnCommit.size(); i++)
		for (Enumeration<Long> e = this.freeChunksOnCommit.keys(); e
				.hasMoreElements();) {
			chunksInUse.put(e.nextElement(), "awaiting commit");
		}
		this.archive.recover(chunksInUse, CorrectErrors);
	}

	public void removeKey(String key) throws Exception {
		long map = this.tree.get(key);
		// this.archive.ReleaseBuffers(map);
		// this.FreeChunksOnCommit.Add(map);
		Long M = new Long(map);
		if (this.freeChunksOnAbort.containsKey(M)) {
			// free it now
			this.freeChunksOnAbort.remove(M);
			this.archive.ReleaseBuffers(map);
		} else {
			// free when committed
			this.freeChunksOnCommit.put(M, M);
		}
		this.tree.removeKey(key);
	}

	public String firstKey() throws Exception {
		return this.tree.firstKey();
	}

	public String nextKey(String AfterThisKey) throws Exception {
		return this.tree.nextKey(AfterThisKey);
	}

	public boolean containsKey(String key) throws Exception {
		return this.tree.containsKey(key);
	}

	public Object get(String key, Object defaultValue) throws Exception {
		long map;
		if (this.tree.containsKey(key)) {
			map = this.tree.lastValueFound;
			return (Object) this.archive.GetChunk(map);
		}
		return defaultValue;
	}

	public void set(String key, Object map) throws Exception {
		// if (!(map is byte[]) )
		// {
		// throw new
		// BplusTreeBadKeyValue("BplusTreeBytes can only archive byte array as value");
		// }
		byte[] thebytes = (byte[]) map;
		// this[key] = thebytes;
		this.set(key, thebytes);
	}

	public void set(String key, byte[] value) throws Exception {
		long storage = this.archive.StoreNewChunk(value, 0, value.length);
		// this.FreeChunksOnAbort.add(new Long(storage));
		Long S = new Long(storage);
		this.freeChunksOnAbort.put(S, S);
		long valueFound;
		if (this.tree.containsKey(key)) {
			valueFound = this.tree.lastValueFound;
			// this.archive.ReleaseBuffers(valueFound);
			Long F = new Long(valueFound);
			if (this.freeChunksOnAbort.containsKey(F)) {
				// free it now
				this.freeChunksOnAbort.remove(F);
				this.archive.ReleaseBuffers(valueFound);
			} else {
				this.freeChunksOnCommit.put(F, F);
			}
		}
		// this.tree[key] = storage;
		this.tree.set(key, storage);
	}

	public byte[] get(String key) throws Exception {
		long map = this.tree.get(key);
		return this.archive.GetChunk(map);
	}

	public void commit() throws Exception {
		// store all new bufferrs
		this.archive.flush();
		// commit the tree
		this.tree.commit();
		// at this point the new buffers have been committed, now free the old
		// ones
		// this.FreeChunksOnCommit.Sort();
		// this.OnCommit.Reverse();
		// foreach (Object thing in this.FreeChunksOnCommit)
		// for (int i=0; i<this.FreeChunksOnCommit.size(); i++)
		for (Enumeration e = this.freeChunksOnCommit.keys(); e
				.hasMoreElements();) {
			long chunknumber = ((Long) e.nextElement()).longValue();
			this.archive.ReleaseBuffers(chunknumber);
		}
		this.archive.flush();
		this.clearBookKeeping();
	}

	public void abort() throws Exception {
		for (Enumeration e = this.freeChunksOnAbort.keys(); e.hasMoreElements();) {
			long chunknumber = ((Long) e.nextElement()).longValue();
			this.archive.ReleaseBuffers(chunknumber);
		}
		this.tree.abort();
		this.archive.flush();
		this.clearBookKeeping();
	}

	/**
	 * 
	 * 
	 * @param limit
	 * @throws Exception
	 */
	public void setFootPrintLimit(int limit) throws Exception {
		this.tree.setFootPrintLimit(limit);
	}

	/**
	 * release resource
	 * 
	 * @throws Exception
	 */
	void clearBookKeeping() throws Exception {
		this.freeChunksOnCommit.clear();
		this.freeChunksOnAbort.clear();
	}

}
